﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace StructuredXmlEditor.View
{
	[TemplatePart(Name = PART_ColorShadingCanvas, Type = typeof(Canvas))]
	[TemplatePart(Name = PART_ColorShadeSelector, Type = typeof(Canvas))]
	[TemplatePart(Name = PART_SpectrumSlider, Type = typeof(ColorSpectrumSlider))]
	[TemplatePart(Name = PART_HexadecimalTextBox, Type = typeof(TextBox))]
	public class ColorCanvas : Control
	{
		private const string PART_ColorShadingCanvas = "PART_ColorShadingCanvas";
		private const string PART_ColorShadeSelector = "PART_ColorShadeSelector";
		private const string PART_SpectrumSlider = "PART_SpectrumSlider";
		private const string PART_HexadecimalTextBox = "PART_HexadecimalTextBox";

		#region Private Members

		private TranslateTransform _colorShadeSelectorTransform = new TranslateTransform();
		private Canvas _colorShadingCanvas;
		private Canvas _colorShadeSelector;
		private ColorSpectrumSlider _spectrumSlider;
		private TextBox _hexadecimalTextBox;
		private Point? _currentColorPosition;
		private bool _surpressPropertyChanged;
		private bool _updateSpectrumSliderValue = true;

		#endregion //Private Members

		#region Properties

		#region SelectedColor

		public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(Color?), typeof(ColorCanvas), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedColorChanged));
		public Color? SelectedColor
		{
			get
			{
				return (Color?)GetValue(SelectedColorProperty);
			}
			set
			{
				SetValue(SelectedColorProperty, value);
			}
		}

		private static void OnSelectedColorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ColorCanvas colorCanvas = o as ColorCanvas;
			if (colorCanvas != null)
				colorCanvas.OnSelectedColorChanged((Color?)e.OldValue, (Color?)e.NewValue);
		}

		protected virtual void OnSelectedColorChanged(Color? oldValue, Color? newValue)
		{
			SetHexadecimalStringProperty(GetFormatedColorString(newValue), false);
			UpdateRGBValues(newValue);
			UpdateColorShadeSelectorPosition(newValue);

			RoutedPropertyChangedEventArgs<Color?> args = new RoutedPropertyChangedEventArgs<Color?>(oldValue, newValue);
			args.RoutedEvent = SelectedColorChangedEvent;
			RaiseEvent(args);
		}

		#endregion //SelectedColor

		#region RGB

		#region A

		public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(byte), typeof(ColorCanvas), new UIPropertyMetadata((byte)255, OnAChanged));
		public byte A
		{
			get
			{
				return (byte)GetValue(AProperty);
			}
			set
			{
				SetValue(AProperty, value);
			}
		}

		private static void OnAChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ColorCanvas colorCanvas = o as ColorCanvas;
			if (colorCanvas != null)
				colorCanvas.OnAChanged((byte)e.OldValue, (byte)e.NewValue);
		}

		protected virtual void OnAChanged(byte oldValue, byte newValue)
		{
			if (!_surpressPropertyChanged)
				UpdateSelectedColor();
		}

		#endregion //A

		#region R

		public static readonly DependencyProperty RProperty = DependencyProperty.Register("R", typeof(byte), typeof(ColorCanvas), new UIPropertyMetadata((byte)0, OnRChanged));
		public byte R
		{
			get
			{
				return (byte)GetValue(RProperty);
			}
			set
			{
				SetValue(RProperty, value);
			}
		}

		private static void OnRChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ColorCanvas colorCanvas = o as ColorCanvas;
			if (colorCanvas != null)
				colorCanvas.OnRChanged((byte)e.OldValue, (byte)e.NewValue);
		}

		protected virtual void OnRChanged(byte oldValue, byte newValue)
		{
			if (!_surpressPropertyChanged)
				UpdateSelectedColor();
		}

		#endregion //R

		#region G

		public static readonly DependencyProperty GProperty = DependencyProperty.Register("G", typeof(byte), typeof(ColorCanvas), new UIPropertyMetadata((byte)0, OnGChanged));
		public byte G
		{
			get
			{
				return (byte)GetValue(GProperty);
			}
			set
			{
				SetValue(GProperty, value);
			}
		}

		private static void OnGChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ColorCanvas colorCanvas = o as ColorCanvas;
			if (colorCanvas != null)
				colorCanvas.OnGChanged((byte)e.OldValue, (byte)e.NewValue);
		}

		protected virtual void OnGChanged(byte oldValue, byte newValue)
		{
			if (!_surpressPropertyChanged)
				UpdateSelectedColor();
		}

		#endregion //G

		#region B

		public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(byte), typeof(ColorCanvas), new UIPropertyMetadata((byte)0, OnBChanged));
		public byte B
		{
			get
			{
				return (byte)GetValue(BProperty);
			}
			set
			{
				SetValue(BProperty, value);
			}
		}

		private static void OnBChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ColorCanvas colorCanvas = o as ColorCanvas;
			if (colorCanvas != null)
				colorCanvas.OnBChanged((byte)e.OldValue, (byte)e.NewValue);
		}

		protected virtual void OnBChanged(byte oldValue, byte newValue)
		{
			if (!_surpressPropertyChanged)
				UpdateSelectedColor();
		}

		#endregion //B

		#endregion //RGB

		#region HexadecimalString

		public static readonly DependencyProperty HexadecimalStringProperty = DependencyProperty.Register("HexadecimalString", typeof(string), typeof(ColorCanvas), new UIPropertyMetadata("", OnHexadecimalStringChanged, OnCoerceHexadecimalString));
		public string HexadecimalString
		{
			get
			{
				return (string)GetValue(HexadecimalStringProperty);
			}
			set
			{
				SetValue(HexadecimalStringProperty, value);
			}
		}

		private static void OnHexadecimalStringChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ColorCanvas colorCanvas = o as ColorCanvas;
			if (colorCanvas != null)
				colorCanvas.OnHexadecimalStringChanged((string)e.OldValue, (string)e.NewValue);
		}

		protected virtual void OnHexadecimalStringChanged(string oldValue, string newValue)
		{
			string newColorString = GetFormatedColorString(newValue);
			string currentColorString = GetFormatedColorString(SelectedColor);
			if (!currentColorString.Equals(newColorString))
			{
				Color? col = null;
				if (!string.IsNullOrEmpty(newColorString))
				{
					col = (Color)ColorConverter.ConvertFromString(newColorString);
				}
				UpdateSelectedColor(col);
			}

			SetHexadecimalTextBoxTextProperty(newValue);
		}

		private static object OnCoerceHexadecimalString(DependencyObject d, object basevalue)
		{
			var colorCanvas = (ColorCanvas)d;
			if (colorCanvas == null)
				return basevalue;

			return colorCanvas.OnCoerceHexadecimalString(basevalue);
		}

		private object OnCoerceHexadecimalString(object newValue)
		{
			var value = newValue as string;
			string retValue = value;

			try
			{
				if (!string.IsNullOrEmpty(retValue))
				{
					ColorConverter.ConvertFromString(value);
				}
			}
			catch
			{
				//When HexadecimalString is changed via Code-Behind and hexadecimal format is bad, throw.
				throw new InvalidDataException("Color provided is not in the correct format.");
			}

			return retValue;
		}

		#endregion //HexadecimalString

		#region UsingAlphaChannel

		public static readonly DependencyProperty UsingAlphaChannelProperty = DependencyProperty.Register("UsingAlphaChannel", typeof(bool), typeof(ColorCanvas), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnUsingAlphaChannelPropertyChanged)));
		public bool UsingAlphaChannel
		{
			get
			{
				return (bool)GetValue(UsingAlphaChannelProperty);
			}
			set
			{
				SetValue(UsingAlphaChannelProperty, value);
			}
		}

		private static void OnUsingAlphaChannelPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ColorCanvas colorCanvas = o as ColorCanvas;
			if (colorCanvas != null)
				colorCanvas.OnUsingAlphaChannelChanged();
		}

		protected virtual void OnUsingAlphaChannelChanged()
		{
			SetHexadecimalStringProperty(GetFormatedColorString(SelectedColor), false);
		}

		#endregion //UsingAlphaChannel

		#endregion //Properties

		#region Constructors

		static ColorCanvas()
		{
			DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorCanvas), new FrameworkPropertyMetadata(typeof(ColorCanvas)));
		}

		#endregion //Constructors

		#region Base Class Overrides

		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			if (_colorShadingCanvas != null)
			{
				_colorShadingCanvas.MouseLeftButtonDown -= ColorShadingCanvas_MouseLeftButtonDown;
				_colorShadingCanvas.MouseLeftButtonUp -= ColorShadingCanvas_MouseLeftButtonUp;
				_colorShadingCanvas.MouseMove -= ColorShadingCanvas_MouseMove;
				_colorShadingCanvas.SizeChanged -= ColorShadingCanvas_SizeChanged;
			}

			_colorShadingCanvas = GetTemplateChild(PART_ColorShadingCanvas) as Canvas;

			if (_colorShadingCanvas != null)
			{
				_colorShadingCanvas.MouseLeftButtonDown += ColorShadingCanvas_MouseLeftButtonDown;
				_colorShadingCanvas.MouseLeftButtonUp += ColorShadingCanvas_MouseLeftButtonUp;
				_colorShadingCanvas.MouseMove += ColorShadingCanvas_MouseMove;
				_colorShadingCanvas.SizeChanged += ColorShadingCanvas_SizeChanged;
			}

			_colorShadeSelector = GetTemplateChild(PART_ColorShadeSelector) as Canvas;

			if (_colorShadeSelector != null)
				_colorShadeSelector.RenderTransform = _colorShadeSelectorTransform;

			if (_spectrumSlider != null)
				_spectrumSlider.ValueChanged -= SpectrumSlider_ValueChanged;

			_spectrumSlider = GetTemplateChild(PART_SpectrumSlider) as ColorSpectrumSlider;

			if (_spectrumSlider != null)
				_spectrumSlider.ValueChanged += SpectrumSlider_ValueChanged;

			if (_hexadecimalTextBox != null)
				_hexadecimalTextBox.LostFocus -= new RoutedEventHandler(HexadecimalTextBox_LostFocus);

			_hexadecimalTextBox = GetTemplateChild(PART_HexadecimalTextBox) as TextBox;

			if (_hexadecimalTextBox != null)
				_hexadecimalTextBox.LostFocus += new RoutedEventHandler(HexadecimalTextBox_LostFocus);

			UpdateRGBValues(SelectedColor);
			UpdateColorShadeSelectorPosition(SelectedColor);

			// When changing theme, HexadecimalString needs to be set since it is not binded.
			SetHexadecimalTextBoxTextProperty(GetFormatedColorString(SelectedColor));
		}

		protected override void OnKeyDown(KeyEventArgs e)
		{
			base.OnKeyDown(e);

			//hitting enter on textbox will update Hexadecimal string
			if (e.Key == Key.Enter && e.OriginalSource is TextBox)
			{
				TextBox textBox = (TextBox)e.OriginalSource;
				if (textBox.Name == PART_HexadecimalTextBox)
					SetHexadecimalStringProperty(textBox.Text, true);
			}
		}

		#endregion //Base Class Overrides

		#region Event Handlers

		void ColorShadingCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			Point p = e.GetPosition(_colorShadingCanvas);
			UpdateColorShadeSelectorPositionAndCalculateColor(p, true);
			_colorShadingCanvas.CaptureMouse();
			//Prevent from closing ColorCanvas after mouseDown in ListView
			e.Handled = true;
		}

		void ColorShadingCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			_colorShadingCanvas.ReleaseMouseCapture();
		}

		void ColorShadingCanvas_MouseMove(object sender, MouseEventArgs e)
		{
			if (e.LeftButton == MouseButtonState.Pressed)
			{
				Point p = e.GetPosition(_colorShadingCanvas);
				UpdateColorShadeSelectorPositionAndCalculateColor(p, true);
				Mouse.Synchronize();
			}
		}

		void ColorShadingCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
		{
			if (_currentColorPosition != null)
			{
				Point _newPoint = new Point
				{
					X = ((Point)_currentColorPosition).X * e.NewSize.Width,
					Y = ((Point)_currentColorPosition).Y * e.NewSize.Height
				};

				UpdateColorShadeSelectorPositionAndCalculateColor(_newPoint, false);
			}
		}

		void SpectrumSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
		{
			if ((_currentColorPosition != null) && (this.SelectedColor != null))
			{
				CalculateColor((Point)_currentColorPosition);
			}
		}

		void HexadecimalTextBox_LostFocus(object sender, RoutedEventArgs e)
		{
			TextBox textbox = sender as TextBox;
			SetHexadecimalStringProperty(textbox.Text, true);
		}

		#endregion //Event Handlers

		#region Events

		public static readonly RoutedEvent SelectedColorChangedEvent = EventManager.RegisterRoutedEvent("SelectedColorChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<Color?>), typeof(ColorCanvas));
		public event RoutedPropertyChangedEventHandler<Color?> SelectedColorChanged
		{
			add
			{
				AddHandler(SelectedColorChangedEvent, value);
			}
			remove
			{
				RemoveHandler(SelectedColorChangedEvent, value);
			}
		}

		#endregion //Events

		#region Methods

		private void UpdateSelectedColor()
		{
			SelectedColor = Color.FromArgb(A, R, G, B);
		}

		private void UpdateSelectedColor(Color? color)
		{
			SelectedColor = ((color != null) && color.HasValue)
							? (Color?)Color.FromArgb(color.Value.A, color.Value.R, color.Value.G, color.Value.B)
							: null;
		}

		private void UpdateRGBValues(Color? color)
		{
			if ((color == null) || !color.HasValue)
				return;

			_surpressPropertyChanged = true;

			A = color.Value.A;
			R = color.Value.R;
			G = color.Value.G;
			B = color.Value.B;

			_surpressPropertyChanged = false;
		}

		private void UpdateColorShadeSelectorPositionAndCalculateColor(Point p, bool calculateColor)
		{
			if (p.Y < 0)
				p.Y = 0;

			if (p.X < 0)
				p.X = 0;

			if (p.X > _colorShadingCanvas.ActualWidth)
				p.X = _colorShadingCanvas.ActualWidth;

			if (p.Y > _colorShadingCanvas.ActualHeight)
				p.Y = _colorShadingCanvas.ActualHeight;

			_colorShadeSelectorTransform.X = p.X - (_colorShadeSelector.Width / 2);
			_colorShadeSelectorTransform.Y = p.Y - (_colorShadeSelector.Height / 2);

			p.X = p.X / _colorShadingCanvas.ActualWidth;
			p.Y = p.Y / _colorShadingCanvas.ActualHeight;

			_currentColorPosition = p;

			if (calculateColor)
				CalculateColor(p);
		}

		private void UpdateColorShadeSelectorPosition(Color? color)
		{
			if ((_spectrumSlider == null) || (_colorShadingCanvas == null) || (color == null) || !color.HasValue)
				return;

			_currentColorPosition = null;

			HsvColor hsv = ColorUtilities.ConvertRgbToHsv(color.Value.R, color.Value.G, color.Value.B);

			if (_updateSpectrumSliderValue)
			{
				_spectrumSlider.Value = hsv.H;
			}

			Point p = new Point(hsv.S, 1 - hsv.V);

			_currentColorPosition = p;

			_colorShadeSelectorTransform.X = (p.X * _colorShadingCanvas.Width) - 5;
			_colorShadeSelectorTransform.Y = (p.Y * _colorShadingCanvas.Height) - 5;
		}

		private void CalculateColor(Point p)
		{
			HsvColor hsv = new HsvColor(360 - _spectrumSlider.Value, 1, 1)
			{
				S = p.X,
				V = 1 - p.Y
			};
			var currentColor = ColorUtilities.ConvertHsvToRgb(hsv.H, hsv.S, hsv.V);
			currentColor.A = A;
			_updateSpectrumSliderValue = false;
			SelectedColor = currentColor;
			_updateSpectrumSliderValue = true;
			SetHexadecimalStringProperty(GetFormatedColorString(SelectedColor), false);
		}

		private string GetFormatedColorString(Color? colorToFormat)
		{
			if ((colorToFormat == null) || !colorToFormat.HasValue)
				return string.Empty;
			return ColorUtilities.FormatColorString(colorToFormat.ToString(), UsingAlphaChannel);
		}

		private string GetFormatedColorString(string stringToFormat)
		{
			return ColorUtilities.FormatColorString(stringToFormat, UsingAlphaChannel);
		}

		private void SetHexadecimalStringProperty(string newValue, bool modifyFromUI)
		{
			if (modifyFromUI)
			{
				try
				{
					if (!string.IsNullOrEmpty(newValue))
					{
						ColorConverter.ConvertFromString(newValue);
					}
					HexadecimalString = newValue;
				}
				catch
				{
					//When HexadecimalString is changed via UI and hexadecimal format is bad, keep the previous HexadecimalString.
					SetHexadecimalTextBoxTextProperty(HexadecimalString);
				}
			}
			else
			{
				//When HexadecimalString is changed via Code-Behind, hexadecimal format will be evaluated in OnCoerceHexadecimalString()
				HexadecimalString = newValue;
			}
		}

		private void SetHexadecimalTextBoxTextProperty(string newValue)
		{
			if (_hexadecimalTextBox != null)
				_hexadecimalTextBox.Text = newValue;
		}

		#endregion //Methods
	}
}
